use std::{path::PathBuf, fs::{File, self}, io::{Write, Read}};

use quote::ToTokens;

pub fn add(left: usize, right: usize) -> usize {
    left + right
}

pub fn write_file(string: &str, project_path: &std::ffi::OsString, ref_path: &str) {
    let mut path_buf = PathBuf::from(project_path);
    path_buf.push(ref_path);

    let generate_path = path_buf.as_path();

    if let Ok(mut generate_file) = File::create(generate_path) {
        generate_file
            .write_all(string.as_bytes())
            .expect(format!("[Generate][Error] Failed to Write({:?})", generate_path).as_str());
    } else {
        println!("[Generate][Warning] Failed to Create({:?})", generate_path);
    }
}

pub fn parse_file(project_path: &std::ffi::OsString, ref_path: &str) -> Result<syn::File, Box<dyn std::error::Error>> {
    // Load Code
    let mut path = PathBuf::from(project_path);
    path.push(ref_path);
    let mut code = String::new();
    match File::open(&path) {
        Ok(mut file) => 
            file.read_to_string(&mut code).expect(format!("[Generate][Error] Failed to Read({:?})", path).as_str()),

        Err(err) => {
            println!("[Generate][Error] Failed to Open({:?})", path);
            return Err(Box::new(err));
        }
    };

    let ast = syn::parse_file(code.as_str())?;
    Ok(ast)
}

macro_rules! USE_STRUCT_FORMAT {
    () => {
r#"
use crate::{};
"#
    };
}

macro_rules! START_IMPL_FORMAT {
    () => {
r#"
impl {} {{ "#
    };
}

macro_rules! END_IMPL_FORMAT {
    () => {
r#"
}
"#
    };
}

macro_rules! GET_STRUCT_NAME_FORMAT {
    () => {
r#"
    pub fn get_struct_name(&self) -> &str {{ 
        "{}" 
    }}
"#
    };
}

macro_rules! GET_FIELD_NAME_FORMAT {
    () => {
r#"
    pub fn get_field_name_{}(&self) -> &str {{ 
        "{}"
    }}
"#
    };
}

macro_rules! GET_FIELD_TYPE_NAME_FORMAT {
    () => {
r#"
    pub fn get_field_type_name_{}(&self) -> &str {{ 
        "{}"
    }}
"#
    };
}

macro_rules! SET_FORMAT {
    () => {
r#"
    pub fn set_{}(&mut self, input: {}) {{ 
        self.{} = input;
    }}
"#
    };
}

macro_rules! GET_FORMAT {
    () => {
r#"
    pub fn get_{}(&self) -> &{} {{ 
        &self.{}
    }}
"#
    };
}

macro_rules! GET_ELEMENT_TYPE_NAME_FORMAT {
    () => {
r#"
    pub fn get_element_type_name_{}(&self) -> &str {{
        "{}"
    }}
"#
    };
}

macro_rules! GET_ARRAY_SIZE_FORMAT {
    () => {
r#"
    pub fn get_array_size_{}(&self) -> usize {{
        self.{}.len()
    }}
"#
    };
}

macro_rules! SET_ELEMENT_FORMAT {
    () => {
r#"
    pub fn set_element_{}(&mut self, index: usize, input: {}) {{
        self.{}[index] = input;
    }}
"#
    };
}

macro_rules! GET_ELEMENT_FORMAT {
    () => {
r#"
    pub fn get_element_{}(&self, index: usize) -> &{} {{
        &self.{}[index]
    }}
"#
    };
}

macro_rules! ADD_ELEMENT_FORMAT {
    () => {
r#"
    pub fn add_element_{}(&mut self, input: {}) {{
        self.{}.push(input);
    }}
"#
    };
}

fn generate_reflection(item: &syn::ItemStruct, project_path: &std::ffi::OsString) {
    let mut output = String::from("");
    let struct_name = item.ident.to_string();

    // Begin
    output.push_str(format!(USE_STRUCT_FORMAT!(), "test_type::test_struct::".to_string() + &struct_name).as_str());
    // output.push_str(format!(USE_STRUCT_FORMAT!(), "test_type").as_str());

    output.push_str(format!(START_IMPL_FORMAT!(), struct_name).as_str());

    // Inner
    output.push_str(format!(GET_STRUCT_NAME_FORMAT!(), struct_name).as_str());
    for field in &item.fields {
        // Get Value Define
        let field_name = field.ident.as_ref().unwrap();
        let field_name_string = field_name.to_string();
        
        let field_type_name_string = field.ty.to_token_stream().to_string().replace(" ", "");
        
        output.push_str(format!(GET_FIELD_NAME_FORMAT!(), field_name_string, field_name_string).as_str());
        output.push_str(format!(GET_FIELD_TYPE_NAME_FORMAT!(), field_name_string, field_type_name_string).as_str());
        output.push_str(format!(SET_FORMAT!(), field_name_string, field_type_name_string, field_name_string).as_str());
        output.push_str(format!(GET_FORMAT!(), field_name_string, field_type_name_string, field_name_string).as_str());

        // Array
        if let syn::Type::Path(path_type) = &field.ty {
            let outer_type_name = &path_type.path.segments[0].ident;

            if outer_type_name.to_string().eq("Vec") {
                let element_type_name = match &path_type.path.segments[0].arguments {
                    syn::PathArguments::AngleBracketed(arguments) => 
                        arguments.args.to_token_stream().to_string().replace(" ", ""),

                    syn::PathArguments::Parenthesized(_) =>
                        String::from("None"),

                    &syn::PathArguments::None =>
                        String::from("None")
                };

                output.push_str(format!(GET_ELEMENT_TYPE_NAME_FORMAT!(), field_name_string, element_type_name).as_str());
                output.push_str(format!(GET_ARRAY_SIZE_FORMAT!(), field_name_string, field_name_string).as_str());
                output.push_str(format!(SET_ELEMENT_FORMAT!(), field_name_string, element_type_name, field_name_string).as_str());
                output.push_str(format!(GET_ELEMENT_FORMAT!(), field_name_string, element_type_name, field_name_string).as_str());
                output.push_str(format!(ADD_ELEMENT_FORMAT!(), field_name_string, element_type_name, field_name_string).as_str());
            }
        }
    }


    // End
    output.push_str(END_IMPL_FORMAT!());
    println!("[Generate][Debug] generate_reflection: {}", output);

    // Output
    write_file(output.as_str(), project_path, format!("./src/_generated/{}.rs", struct_name).as_str())

}

pub fn run(project_path: &std::ffi::OsString) {
    println!("[Generate][Info] In Path({:?})", project_path);
    // Path Check
    let mut dir_path = PathBuf::from(project_path);
    dir_path.push("./src/_generated");
    if std::fs::metadata(&dir_path).is_ok() {
        fs::remove_dir_all(&dir_path).expect("");
    }

    return;

    fs::create_dir(&dir_path)
        .expect(format!("[Generate][Warning] Failed to Create Folder({:?})", dir_path).as_str());

    // Open File
    let output = r#"pub mod TestStruct;
"#;
    write_file(output, project_path, "./src/_generated/mod.rs");

    // 
    if let Ok(ast) = &parse_file(project_path, "./src/test_type/test_struct.rs") {
        for item in &ast.items {
            match item {
                syn::Item::Struct(struct_item) => {
                    // if need to 
                    let is_need_deal;
                    is_need_deal = struct_item.attrs.iter().any(
                        |attr|
                        if let syn::Meta::Path(path) = &attr.meta {
                            path.segments.iter().any(
                                |segment|
                                segment.ident.to_string().eq("proc_macro_reflection")
                            )
                        } else {
                            false
                        }
                    );

                    // run 
                    if is_need_deal {
                        generate_reflection(struct_item, project_path);
                    }

                },
                _ => {},
            }
        }
    }

    // generate mod.rs

}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let result = add(2, 2);
        assert_eq!(result, 4);
    }
}
